package jmine.tec.aom.domain.impl;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;

import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Transient;

import jmine.tec.aom.domain.AbstractPropertyValue;
import jmine.tec.aom.domain.EntityProperty;
import jmine.tec.aom.exception.FillException;
import jmine.tec.persist.annotation.DiscriminatorComment;
import jmine.tec.utils.Tuple;
import jmine.tec.utils.reflection.AnnotationUtils;

import org.apache.commons.beanutils.PropertyUtils;

import bancosys.tec.component.Documentation;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.persist.persister.annotation.NaturalKey;

/**
 * {@link AbstractPropertyValue} implementation for the {@link Persistable} type.
 * <p>
 * Refer to {@link EntityProperty} for additional information.
 * 
 * @author jrenaut
 */
@Entity
@DiscriminatorValue("17")
@DiscriminatorComment("ENTIDADE")
public class EntityPropertyValue extends AbstractPropertyValue<Persistable> {

    private String naturalKeyValue;

    /**
     * @return the naturalKeyValue
     */
    @Column(name = "VL_NATURAL_KEY_STR", length = 255)
    @Documentation("VALOR DA CARACTERISTICA DO TIPO NATURAL KEY ARMAZENADO COMO STRING.")
    public String getNaturalKeyValue() {
        return this.naturalKeyValue;
    }

    /**
     * @param naturalKeyValue the naturalKeyValue to set
     */
    public void setNaturalKeyValue(String naturalKeyValue) {
        this.naturalKeyValue = naturalKeyValue;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transient
    public Persistable getValue() {
        if (this.naturalKeyValue == null) {
            return null;
        }

        EntityProperty ep = this.getController().getDAOFactory().getGenericDAO(EntityProperty.class).narrow(this.getProperty());
        Class<? extends Persistable> persistableClass;
        try {
            persistableClass = ep.getEntityClass();
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("The required persistable class was not found for property " + this.getPropertyName(), e);
        }

        BaseDAO<Persistable> entityDAO = this.getController().getDAOFactory().getGenericDAO(persistableClass);
        PropertyDescriptor pd = getNaturalKeyProperty(persistableClass);
        if (pd != null) {
            try {
                return entityDAO.findByNaturalKey(this.convert(this.naturalKeyValue, pd.getPropertyType()));
            } catch (BeanNotFoundException e) {
                return null;
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void doSetValue(String stringValue) throws FillException {
        this.naturalKeyValue = stringValue;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String doGetValueAsString() {
        return this.naturalKeyValue;
    }

    /**
     * @param s a value
     * @param propertyType the natural key type
     * @return the properly converted value
     */
    private Serializable convert(String s, Class<?> propertyType) {
        if (String.class.isAssignableFrom(propertyType)) {
            return s;
        } else if (Long.class.isAssignableFrom(propertyType)) {
            return Long.valueOf(s);
        }

        // shouldn't happen since we're reading from database... unless there's been outside interference
        throw new IllegalArgumentException("The @NaturalKey type " + propertyType.getCanonicalName()
                + " is currently not supported for use with property values.");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setValue(Persistable value) {
        Collection<Tuple<Method, NaturalKey>> methods = AnnotationUtils.findMethodsWithAnnotation(value.getClass(), NaturalKey.class);
        if (!methods.isEmpty()) {
            Method method = methods.iterator().next().getHead();
            try {
                Object obj = method.invoke(value);
                if (obj != null) {
                    this.naturalKeyValue = obj.toString();
                } else {
                    this.naturalKeyValue = null;
                }
            } catch (Exception e) {
                this.naturalKeyValue = null;
            }
        }

    }

    /**
     * @param clazz a {@link Persistable} class
     * @return a {@link PropertyDescriptor}
     */
    private static PropertyDescriptor getNaturalKeyProperty(Class<? extends Persistable> clazz) {
        PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(clazz);
        for (final PropertyDescriptor property : properties) {
            Method getter = PropertyUtils.getReadMethod(property);
            if (getter != null && getter.isAnnotationPresent(NaturalKey.class)) {
                return property;
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected AbstractPropertyValue<Persistable> createBean() {
        EntityPropertyValue bean = (EntityPropertyValue) super.createBean();
        bean.setNaturalKeyValue(this.getNaturalKeyValue());
        return bean;
    }

}
